package com.anji.plus.gaea;

import com.alibaba.fastjson.JSONObject;
import com.anji.plus.gaea.bean.ResponseBean;
import com.anji.plus.gaea.cache.CacheHelper;
import com.anji.plus.gaea.cache.GaeaCacheService;
import com.anji.plus.gaea.cache.RedisCacheHelper;
import com.anji.plus.gaea.config.MybatisPlusMetaObjectHandler;
import com.anji.plus.gaea.constant.GaeaConstant;
import com.anji.plus.gaea.curd.mapper.injected.CustomSqlInjector;
import com.anji.plus.gaea.event.listener.ExceptionApplicationListener;
import com.anji.plus.gaea.exception.ErrorFieldResolver;
import com.anji.plus.gaea.holder.UserContentHolder;
import com.anji.plus.gaea.holder.UserContext;
import com.anji.plus.gaea.i18.MessageLocaleResolver;
import com.anji.plus.gaea.i18.MessageSourceHolder;
import com.anji.plus.gaea.init.InitRequestUrlMappings;
import com.anji.plus.gaea.intercept.AccessKeyInterceptor;
import com.anji.plus.gaea.introspector.DateFormatterAnnotationDeSerializerIntrospector;
import com.anji.plus.gaea.introspector.DateFormatterAnnotationSerializerIntrospector;
import com.anji.plus.gaea.utils.ApplicationContextUtils;
import com.anji.plus.gaea.utils.JwtBean;
import com.auth0.jwt.interfaces.Claim;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 盖亚自动装配
 * @author lr
 * @since 2021-01-11
 */
@Configuration
@EnableConfigurationProperties(GaeaProperties.class)
public class GaeaAutoConfiguration {

    /**
     * spring上下文工具类
     * @return
     */
    @Bean
    public ApplicationContextUtils applicationContextUtils() {
        return new ApplicationContextUtils();
    }

    /**
     * jwt实例
     * @param gaeaProperties
     * @return
     */
    @Bean
    public JwtBean jwtBean(GaeaProperties gaeaProperties) {
        return new JwtBean(gaeaProperties);
    }

    @Bean
    @ConditionalOnClass(RedisAutoConfiguration.class)
    @ConditionalOnMissingBean
    public CacheHelper cacheHelper() {
        return new RedisCacheHelper();
    }

    @Bean
    @ConditionalOnMissingBean(GaeaCacheService.class)
    public GaeaCacheService cacheService(){
        return new GaeaCacheService() {};
    }

    /**
     * web服务器环境,网关不加载
     */
    @Configuration
    @ConditionalOnClass(WebMvcConfigurer.class)
    @ComponentScan(value = {"com.anji.plus.gaea.controller", "com.anji.plus.gaea.exception.advice"})
    public static class WebGaeaAutoConfiguration {
        /**
         * 获取当前应用所有的RequestMapping信息，用于权限配置
         * @return
         */
        @Bean
        public InitRequestUrlMappings initRequestUrlMappings() {
            return new InitRequestUrlMappings();
        }

        /**
         * 解析token用户名
         * @return
         */
        @Bean
        public FilterRegistrationBean registrationBean(JwtBean jwtBean) {
            FilterRegistrationBean registrationBean = new FilterRegistrationBean();
            registrationBean.setFilter((request, response, chain) -> {
                if (request instanceof HttpServletRequest) {
                    HttpServletRequest req = (HttpServletRequest) request;
                    String authorization = req.getHeader(GaeaConstant.Authorization);

                    String orgCode = req.getHeader(GaeaConstant.ORG_CODE);
                    String sysCode = req.getHeader(GaeaConstant.SYS_CODE);
                    String locale = req.getHeader(GaeaConstant.LOCALE);
                    String timeZone = req.getHeader(GaeaConstant.TIME_ZONE);
                    UserContext userContext = UserContentHolder.getContext();
                    //语言标识
                    if (StringUtils.isNotBlank(locale)) {
                        userContext.setLocale(Locale.forLanguageTag(locale));
                    }
                    //时区
                    if (StringUtils.isNotBlank(timeZone)) {
                        userContext.setTimeZone(timeZone);
                    }

                    if (StringUtils.isNotBlank(authorization)) {
                        try {
                            Map<String,Claim> authMap = jwtBean.getClaim(authorization);
                            String username = authMap.get("username").asString();//jwtBean.getUsername(authorization);
                            Integer userType = authMap.get("type").asInt();//jwtBean.getUserType(authorization);
                            String tenant = authMap.get("tenant").asString();//jwtBean.getTenant(authorization);
                            // String uuid = jwtBean.getUUID(authorization);

                            userContext.setUsername(username);
                            userContext.setType(userType);
                            userContext.setTenantCode(tenant);
                            MDC.put(GaeaConstant.USER_NAME, username);

                            //组织
                            if (StringUtils.isNotBlank(orgCode)) {
                                userContext.setOrgCode(orgCode);
                            }

                            //终端标识：web还是移动端
                            if(StringUtils.isNotBlank(sysCode)) {
                                userContext.setSysCode(sysCode);
                            }
                            userContext.getParams().put(GaeaConstant.TENANT_CODE, tenant);
                            userContext.getParams().put(GaeaConstant.ORG_CODE, orgCode);
                        } catch (Exception e) {
                            ResponseBean responseBean = ResponseBean.builder()
                                    .code("User.credentials.expired")
                                    .message("The Token has expired")
                                    .args(new String[]{e.getMessage()}).build();
                            response.getWriter().print(JSONObject.toJSONString(responseBean));
                            return;
                        }
                    }
                }
                try {
                    chain.doFilter(request, response);
                }finally {
                    //清空上下文
                    UserContentHolder.clearContext();
                }
            });
            registrationBean.addUrlPatterns("/*");
            registrationBean.setName("userOrgCodeFilter");
            registrationBean.setOrder(Integer.MIN_VALUE + 100);
            return registrationBean;
        }

        /**
         * 国际化
         *
         * @author lr
         * @since 2021-01-01
         */
        @Configuration
        @ConditionalOnClass(LocaleResolver.class)
        @ConditionalOnMissingBean(MessageLocaleResolver.class)
        public class MessageI18AutoConfiguration {

            /**
             * 根据请求头识别国际化locale
             *
             * @return
             */
            @Bean
            @Primary
            public MessageLocaleResolver localeResolver() {
                return new MessageLocaleResolver();
            }

            /**
             * 国际化
             *
             * @return
             */
            @Bean
            public MessageSourceHolder messageSourceHolder() {
                return new MessageSourceHolder();
            }
        }
    }

    /**
     * 解决时区问题 序列号和反序列化
     * @ConditionalOnProperty通过配置文件控制当前bean是否生效
     * @return
     */
    @Bean
    @ConditionalOnProperty(name="customer.timezone.serializer.enable", havingValue="true")
    public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
        // Config the Json convert Chinese garbled.
        // 这里的配置可能会导致application.properties文件中spring.jackson.time-zone=GMT+8失效
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();

        // 不设置Utf-8格式,可能会导致Mock测试输出信息乱码
        converter.setDefaultCharset(StandardCharsets.UTF_8);

        ObjectMapper objectMapper = converter.getObjectMapper();
        objectMapper.setTimeZone(TimeZone.getDefault());
        objectMapper.setAnnotationIntrospectors(new DateFormatterAnnotationSerializerIntrospector(), new DateFormatterAnnotationDeSerializerIntrospector());
        return converter;
    }

    /**
     * 异常监听
     * @return
     */
    @Bean
    public ExceptionApplicationListener exceptionApplicationListener() {
        return new ExceptionApplicationListener();
    }

    /**
     * Web配置
     */
    @Configuration
    @ConditionalOnClass(WebMvcConfigurer.class)
    public static class GaeaWebMvcConfigurer implements WebMvcConfigurer{

        /**
         * 拦截器
         * @param registry
         */
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            InterceptorRegistration interceptorRegistration = registry.addInterceptor(new AccessKeyInterceptor());
            interceptorRegistration.addPathPatterns("/**");
        }
    }

    /**
     * 持久层mybatis-plus自动装配
     *
     * @author lr
     * @since 2021-01-01
     */
    @Configuration
    @ConditionalOnClass(MybatisPlusAutoConfiguration.class)
    public static class GaeaMybatisPlusAutoConfiguration {
        /**
         * 乐观锁，需要在version字段上加@Version
         * 3.3.2
         * @return
         */
        /*@Bean
        public OptimisticLockerInterceptor optimisticLockerInterceptor() {
            return new OptimisticLockerInterceptor();
        }*/

        @Bean
        public OptimisticLockerInnerInterceptor optimisticLockerInterceptor(){
            return new OptimisticLockerInnerInterceptor();
        }
        /**
         * 分页插件,3.5.2
         * @return
         */
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(DbType dbType) {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(paginationInterceptor(dbType));
            interceptor.addInnerInterceptor(optimisticLockerInterceptor());
            return interceptor;
        }
        @Bean
        @ConditionalOnMissingBean
        public DbType dbType(){
            return DbType.MYSQL;
        }

        /**
         * 填充sql
         *
         * @return
         */
        @Bean
        public CustomSqlInjector customSqlInjector() {
            return new CustomSqlInjector();
        }

        /**
         * 分页
         *
         * @return
         */
        @Bean
        @ConditionalOnMissingBean(value = PaginationInnerInterceptor.class)
        public PaginationInnerInterceptor paginationInterceptor(DbType dbType) {
            return new PaginationInnerInterceptor(dbType);
        }

        /**
         * 默认填充
         *
         * @return
         */
        @Bean
        @ConditionalOnMissingBean(value = {MetaObjectHandler.class})
        public MybatisPlusMetaObjectHandler mybatisPlusMetaObjectHandler() {
            return new MybatisPlusMetaObjectHandler();
        }
    }

    @Bean
    @ConditionalOnMissingBean(ErrorFieldResolver.class)
    public ErrorFieldResolver errorFieldResolver(){
        return new ErrorFieldResolver() {
            @Override
            public String getFieldName(String fieldName, MethodParameter parameter) {
                return ErrorFieldResolver.super.getFieldName(fieldName, parameter);
            }
        };
    }

    @Bean
    @ConditionalOnMissingBean
    public SimpleApplicationEventMulticaster gaeaAsyncApplicationEventMulticaster(
            ApplicationContext applicationContext){
        SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(
                applicationContext.getAutowireCapableBeanFactory());
        int processors = Runtime.getRuntime().availableProcessors();
        //设置异步执行
        eventMulticaster.setTaskExecutor(new ThreadPoolExecutor(processors,
                        processors,
                        10,
                        TimeUnit.MINUTES,
                        new ArrayBlockingQueue<>(16 * processors, true)));
        return eventMulticaster;
    }
}
